"use client"; import { type ReactNode, use, useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { ArrowLeft, Wrench, AlertTriangle, ChevronRight, Trash2, Clock, FolderGit2, Copy, Check, Sparkles, } from "lucide-react"; import { PageHeader } from "@/components/common/page-header"; import { HostBadge } from "@/components/common/host-badge"; import { LearningsBadge } from "@/components/common/learnings-badge"; import { EmptyState } from "@/components/common/empty-state"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { formatTimestamp, truncateId } from "@/lib/format"; import type { CitedItem, SessionDetail } from "@/lib/types"; const MARKDOWN_LINK_PATTERN = /\[([^\]\n]+)\]\((https?:\/\/[^\s)]+|\/[^\s)]*)\)/g; export default function InteractionDetailPage({ params, }: { params: Promise<{ sessionId: string }>; }) { const { sessionId } = use(params); const router = useRouter(); const [detail, setDetail] = useState(null); const [error, setError] = useState(null); const [deleting, setDeleting] = useState(false); const remove = async () => { if (!confirm(`Delete session ${sessionId}? This cannot be undone.`)) return; setDeleting(true); try { const res = await fetch( `/api/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" }, ); if (!res.ok) throw new Error(`delete failed: ${res.status}`); router.push("/sessions"); } catch (e) { setError(e instanceof Error ? e.message : String(e)); setDeleting(false); } }; useEffect(() => { let cancelled = false; fetch(`/api/sessions/${encodeURIComponent(sessionId)}`, { cache: "no-store" }) .then(async (r) => { if (!r.ok) throw new Error(`failed: ${r.status}`); return r.json(); }) .then((data) => { if (!cancelled) setDetail(data); }) .catch((e) => { if (!cancelled) setError(e instanceof Error ? e.message : String(e)); }); return () => { cancelled = true; }; }, [sessionId]); return (
{sessionId} {detail && ( <> )} } actions={
} />
{error && ( )} {!error && detail && detail.turns.length === 0 && ( )} {detail && detail.turns.length > 0 && (
{detail.turns.map((turn, idx) => { const isUser = turn.role === "User"; const flagged = turn.user_action && turn.user_action !== "NONE"; return (
{turn.role} {flagged && ( {turn.user_action} )} {turn.tools_used && turn.tools_used.length > 0 && (
{turn.tools_used.map((t, ti) => { const input = t.tool_data?.input; const output = t.tool_data?.output; const hasInput = input && Object.keys(input).length > 0; const hasOutput = typeof output === "string" && output.length > 0; if (!hasInput && !hasOutput) { return ( {t.tool_name} ); } return (
{t.tool_name}
{hasInput && (
input
                                        {JSON.stringify(input, null, 2)}
                                      
)} {hasOutput && (
output
                                        {output}
                                      
)}
); })}
)}
                    
                  
{turn.user_action_description && (

{turn.user_action_description}

)} {turn.cited_items && turn.cited_items.length > 0 && ( )}
); })}
Published up to turn {detail.published_up_to}
)}
); } function MarkdownLinkedText({ text }: { text: string }) { const parts: ReactNode[] = []; let cursor = 0; for (const match of text.matchAll(MARKDOWN_LINK_PATTERN)) { const matchIndex = match.index ?? 0; const [raw, label, href] = match; if (matchIndex > cursor) { parts.push(text.slice(cursor, matchIndex)); } parts.push( {label} , ); cursor = matchIndex + raw.length; } if (parts.length === 0) return text; if (cursor < text.length) { parts.push(text.slice(cursor)); } return <>{parts}; } function CitedItemsRow({ items }: { items: CitedItem[] }) { return (
Used {items.map((item) => { const targetId = item.real_id ?? item.id; const href = item.kind === "playbook" ? item.source_kind === "agent_playbook" ? `/skills/shared/${encodeURIComponent(targetId)}` : `/skills/project/${encodeURIComponent(targetId)}` : `/preferences/project/${encodeURIComponent(targetId)}`; return ( {item.title || item.id} ); })}
); } function TurnMeta({ ts, userId }: { ts?: number; userId?: string }) { if (ts === undefined && !userId) return null; return (
{ts !== undefined && (
{formatTimestamp(ts)}
)} {userId && (
Project
{truncateId(userId, 32, 8)}
)}
); } function CopyButton({ value }: { value: string }) { const [copied, setCopied] = useState(false); const copy = async () => { try { await navigator.clipboard.writeText(value); setCopied(true); setTimeout(() => setCopied(false), 1200); } catch { // ignore } }; return ( ); }